package com.smsc.headend.task.engine.executor.impl.firmware;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.smsc.headend.common.enums.errorcode.TaskExecuteErrorCode;
import com.smsc.headend.common.utils.RedisKeys;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.dmo.ComConfigDMO;
import com.smsc.headend.module.asset.dmo.MeterConfigDMO;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.common.entity.DeviceOperation;
import com.smsc.headend.module.data.dmo.DeviceSAPandKeys;
import com.smsc.headend.module.data.dto.AnsiSubDataItemDTO;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.data.dto.TaskDataDTO;
import com.smsc.headend.module.data.enums.DictionaryType;
import com.smsc.headend.module.fwu.entity.FirmwareFile;
import com.smsc.headend.module.fwu.entity.TemplateFwUpgrade;
import com.smsc.headend.module.protocol.ProtocolType;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.dto.Task;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.task.engine.exception.UDISTaskExecutorException;
import com.smsc.headend.task.engine.executor.TaskExecutor;
import com.smsc.headend.task.engine.facade.DataItemFacade;
import com.smsc.headend.task.engine.handler.impl.ActionSuccessHandler;
import com.smsc.headend.task.engine.handler.impl.firmware.FirmwareSuccessHandler;
import com.smsc.headend.task.engine.mapper.FirmwareFileMapper;
import com.smsc.headend.module.task.dto.AtomicTaskState;
import com.smsc.headend.task.engine.service.ConnectorService;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import com.smsc.headend.task.engine.service.firmware.FirmwareService;
import com.smsc.headend.task.engine.service.task.TaskPacketService;
import com.smsc.headend.task.engine.utils.AnsiUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author liangli
 * @date 2020/9/24
 */
@Slf4j
public class ImageTransferInitExecutor implements TaskExecutor {
    @Autowired
    TaskPacketService packetService;
    @Autowired
    ConnectorService connectorService;
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    FeignAssetManagementService assetManagementRemoteService;
    @Autowired
    DataItemFacade dataItemFacade;
    @Autowired
    TaskDAO taskDAO;
    @Autowired
    FirmwareFileMapper firmwareFileMapper;
    @Autowired
    AssetService assetService;
    @Autowired
    FirmwareService firmwareService;

    @Override
    public AtomicTaskState execute(Task task, AtomicTask atomicTask) throws UDISTaskExecutorException {
        Map<String, Object> paramMap = this.getParameterMap(task, atomicTask);
        if (!paramMap.containsKey("fwFileId")) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.FW_FILE_NOT_SPECIFIED.toString());
        }
        Meter meter = getMeter(task, assetService);
        ComEntity terminal = assetService.getComById(task.getComId());
        ComConfigDMO comConfigDMO = assetService.getComConfigById(task.getComId());
        if (task.getMeterId() != null && meter == null) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.METER_NOT_EXISTS.toString());
        }
        Long fwFileId = Convert.toLong(paramMap.get("fwFileId"));
        FirmwareFile firmwareFile = firmwareFileMapper.selectById(fwFileId);
        Long fileSize = firmwareFile.getFwFileSize();
        String identifier = firmwareFile.getFwFileIdentifier();
        Integer blockSize;
        if(comConfigDMO.getProtocolId() == ProtocolType.ANSI_C12_18_Protocol.getProtocolId()) {
            blockSize = Convert.toInt(assetService.getSystemProperty("MAX_BLOCK_SIZE"));
            int ret = 0;
            try {
                ret = initFileToCacheAnsi(firmwareFile, blockSize);
            } catch (SQLException throwables) {
                log.error("init file failed, fileId={}", firmwareFile.getFwFileId());
            }
            if (ret < 0) {
                throw new UDISTaskExecutorException(TaskExecuteErrorCode.FW_FILE_ILLEGAL.toString());
            }
            AnsiSubDataItemDTO dataItemDTO = new AnsiSubDataItemDTO();
            dataItemDTO.setTableId(7);
            dataItemDTO.setProcedureId(10);
            dataItemDTO.setRoleId(2);
            byte[] packets = packetService.ansiWriteFullTable(7, new byte[]{0x08, 0x0A, 0x00}, meter.getMeterId());
            log.debug(ByteUtils.byteToHexString(packets));
            connectorService.sendPacket(task, atomicTask, 2, task.getIsODR(), packets);
        } else {
            List<TaskDataDTO> dataDTOS = taskDAO.getAllTaskData(task.getTaskNo());
            TaskDataDTO dataDTO = dataDTOS.stream().filter(taskDataDTO -> taskDataDTO.getKey().equalsIgnoreCase("blockSize")).findAny().get();
            blockSize = Integer.valueOf(String.valueOf(dataDTO.getValue()));
            int ret = 0;
            try {
                ret = initFileToCache(firmwareFile, blockSize);
            } catch (SQLException throwables) {
                log.error("init file failed, fileId={}", firmwareFile.getFwFileId());
            }
            if (ret < 0) {
                throw new UDISTaskExecutorException(TaskExecuteErrorCode.FW_FILE_ILLEGAL.toString());
            }
            DeviceOperation dictionary = assetService.getDictionary(atomicTask.getOperationId(), DictionaryType.Operation.getType());
            String interfaceType = comConfigDMO.getInterfaceType();
            Boolean isShortName = comConfigDMO.getIsShortName();
            MeterConfigDMO meterConfigDMO = null;
            if (task.getMeterId() != null) {
                meterConfigDMO = assetService.getMeterConfigById(task.getMeterId());
                interfaceType = meterConfigDMO.getInterfaceType();
                isShortName = meterConfigDMO.getIsShortName();
            }
            log.info("ImageTransfer: start init meterId={}, taskNo={}, param={}", task.getMeterId(), task.getTaskNo(), JSONUtil.toJsonStr(getParameterMap(task, atomicTask)));
            List<DataItemDTO> dataItemDTOList = dataItemFacade.getDataItemsByOperation(meter, terminal, dictionary, paramMap, comConfigDMO.getModelId());
            DeviceSAPandKeys deviceKeys = meter != null ? assetManagementRemoteService.getMeterCommunicationKey(meter.getMeterId(), dataItemDTOList.get(0).getRoleId()) :
                    assetManagementRemoteService.getTerminalCommunicationKey(terminal.getComId(), dataItemDTOList.get(0).getRoleId());
            byte[] packets = packetService.imageTransferInitAction(dataItemDTOList.get(0), interfaceType, isShortName, deviceKeys.getWPort().intValue(), identifier, fileSize);
            connectorService.sendPacket(task, atomicTask, dataItemDTOList.get(0).getRoleId(), task.getIsODR(), packets);
        }

        AtomicTaskState state = taskDAO.hashGetAtomicTaskState(atomicTask.getAtomicTaskNo());
        if (state != null) {
            state.setStored(true);
            return state;
        }
        return  createAtomicState(FirmwareSuccessHandler.class);
    }

    private int initFileToCache(FirmwareFile firmwareFile, Integer blockSize) throws SQLException {
        String fwCacheKey = RedisKeys.getTaskFirmwareFile(firmwareFile.getFwFileId(), blockSize);
        if (redisUtils.hasKey(fwCacheKey)) {
            return 1;
        }

        byte[] bytes = firmwareFile.getFwFile();
        int blockNumber = bytes.length / blockSize;
        int lastBlockSize = bytes.length % blockSize;
        log.info("fw init: firmwareSize={}, actualSize={}, blockSize={}, ", firmwareFile.getFwFileSize(), bytes.length, blockSize);
        for (int i = 0; i < blockNumber; i++) {
            int blockSizeInPacket = blockSize;
            if (i == blockNumber - 1 && lastBlockSize != 0) {
                blockSizeInPacket = lastBlockSize;
            }
            byte[] blockBytes = new byte[blockSizeInPacket];
            // log.debug("fw init: blockNumber={}, size={}", i, blockSizeInPacket);
            System.arraycopy(bytes, i * blockSize, blockBytes, 0, blockSizeInPacket);
            redisUtils.hset(fwCacheKey, String.valueOf(i), ByteUtils.byteToHexString(blockBytes));
        }
        return 1;
    }

    private int initFileToCacheAnsi(FirmwareFile firmwareFile, Integer blockSize) throws SQLException {
        String fwCacheKey = RedisKeys.getTaskFirmwareFile(firmwareFile.getFwFileId(), blockSize);
        if (redisUtils.hasKey(fwCacheKey)) {
            return 1;
        }

        byte[] bytes = firmwareFile.getFwFile();
        String dataStr = new String(bytes, StandardCharsets.UTF_8);
        log.info("fw init: firmwareSize={}, actualSize={}, blockSize={}, ", firmwareFile.getFwFileSize(), bytes.length, blockSize);
        blockSize = blockSize * 2;
        dataStr = dataStr.replaceAll(":", "3A").replaceAll(StrUtil.CRLF, "");
        List<String> list = getStrList(dataStr, blockSize);
        int blockNumber = list.size();
        for (int i = 0; i < blockNumber; i++) {
            redisUtils.hset(fwCacheKey, String.valueOf(i), list.get(i));
        }
        return 1;
    }

    /**
     * 把原始字符串分割成指定长度的字符串列表
     *
     * @param inputString
     *            原始字符串
     * @param length
     *            指定长度
     * @return
     */
    private List<String> getStrList(String inputString, int length) {
        int size = inputString.length() / length;
        if (inputString.length() % length != 0) {
            size += 1;
        }
        return getStrList(inputString, length, size);
    }

    /**
     * 把原始字符串分割成指定长度的字符串列表
     *
     * @param inputString
     *            原始字符串
     * @param length
     *            指定长度
     * @param size
     *            指定列表大小
     * @return
     */
    private List<String> getStrList(String inputString, int length,
                                          int size) {
        List<String> list = new ArrayList<String>();
        for (int index = 0; index < size; index++) {
            String childStr = substring(inputString, index * length,
                    (index + 1) * length);
            list.add(childStr);
        }
        return list;
    }

    /**
     * 分割字符串，如果开始位置大于字符串长度，返回空
     *
     * @param str
     *            原始字符串
     * @param f
     *            开始位置
     * @param t
     *            结束位置
     * @return
     */
    private String substring(String str, int f, int t) {
        if (f > str.length())
            return null;
        if (t > str.length()) {
            return str.substring(f);
        } else {
            return str.substring(f, t);
        }
    }

    @Override
    public Boolean handleFailure(Task task, AtomicTask atomicTask, String message) {
        if (UDISTaskExecutorException.COM_STATUS_EXCEPTION.equalsIgnoreCase(message)) {
            log.warn("fwu init pause because of com offline");
            return false;
        }
        firmwareService.saveFwuReport(task, atomicTask, -1, TemplateFwUpgrade.TaskType.ImageTransfer, "fwu init execute failed", null);
        return true;
    }
}
